package cn.edu.sjtu;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;

import cn.edu.sjtu.dfa.*;

/**
 * the builder to turn instruction list into a directed graph.
 * predecessors for DFA generated
 * @author Seabert
 *
 */
public class GraphBuilder {

	private List<Instruction> instrList;
	private Instruction root = null;
	public final static int binop = 1,call = 2, callwithreturn = 3, cjump = 4, jump = 5, label = 6, move = 7, ret = 8 , pairmove = 9;
	public final static int error = 99999;
	
	public ArrayList<ArrayList<Integer>> predecessors;
	
	public GraphBuilder(List<Instruction> instrList){
		this.instrList = instrList;
		if(this.instrList != null){
			predecessors = new ArrayList<ArrayList<Integer>>();
			for(int i = 0; i< instrList.size(); i++){
				predecessors.add(i, new ArrayList<Integer>());
			}
		}
		
	}
	
	
	/**
	 * replace the labels in the jumps by the real one
	 * build the predecessor mapping
	 */
	public void buildGraph(){
		if(this.instrList != null){
			Instruction thePredecessor = null;
			int index = 0;
			for(Instruction instr:instrList){
				
				if(thePredecessor != null && getInstructionTypeNum(thePredecessor) != GraphBuilder.ret
						&& getInstructionTypeNum(thePredecessor) != GraphBuilder.jump){
					
					ArrayList<Integer> tmpList = predecessors.get(index);
						tmpList.add(index-1);
					
						
				}
				switch(getInstructionTypeNum(instr)){
				case binop:
					//TODO real calculation
					break;
					
				case call:
					//CFG extension
					break;
				case callwithreturn:
					//CFG extension
					break;
				case cjump:
					boolean l1Done = false;
					//boolean l2Done = false;
					for(Instruction labelInst:instrList){
						if(getInstructionTypeNum(labelInst) == label){
							
							if(((Label)labelInst).getLabel().equals(((CJump)instr).getLabel1().getLabel())  ){
								((CJump)instr).setLabel1((Label) labelInst);
//								System.out.println("THere there" + ((Label)labelInst).getLabel() + "   " + ((CJump)instr).getLabel1().getLabel());
								if(!l1Done){
									l1Done = true;
									
								}
								else
									System.out.println("ERROR: Duplicate Label1!: " + ((Label)labelInst).toString() );
							}
							/*if(((Label)labelInst).getLabel().equals(((CJump)instr).getLabel2().getLabel()) ){
								((CJump)instr).setLabel2((Label) labelInst);
								if(!l2Done){
									l2Done = true;
								}							
								else
									System.out.println("ERROR: Duplicate Label2!: " + ((Label)labelInst).toString());
							}*/
						}
					}
					if(l1Done){
						ArrayList<Integer> tmpList = predecessors.get(instrList.indexOf(((CJump)instr).getLabel1()));
						tmpList.add(instrList.indexOf(instr));
					}
					else{
						System.out.println("ERROR: Label1 not Exist! : " + ((CJump)instr).getLabel1().toString());
					}
					/*if(l2Done){
						ArrayList<Integer> tmpList = predecessors.get(instrList.indexOf(((CJump)instr).getLabel2()));
						tmpList.add(instrList.indexOf(instr));
					}
					else{
						System.out.println("ERROR: Label2 not Exist! : " + ((CJump)instr).getLabel2().toString());
					}*/
					
					break;
				case jump:
					boolean lDone = false;
					for(Instruction labelInst:instrList){
						if(getInstructionTypeNum(labelInst) == label){
							if(((Label)labelInst).getLabel().equals(((Jump)instr).getLabel().getLabel())){
								((Jump)instr).setLabel((Label) labelInst);
								if(!lDone){
									lDone = true;
								}							
								else
									System.out.println("ERROR: Duplicate Label!: " + ((Label)labelInst).toString());
							}
							
						}
					}
					if(lDone){
						ArrayList<Integer> tmpList = predecessors.get(instrList.indexOf((((Jump)instr).getLabel())));
						tmpList.add(instrList.indexOf(instr));
					}
					else{
						System.out.println("ERROR: Label not Exist! : " + ((Jump)instr).getLabel().toString());
					}
					break;
				case label:
					break;
				case move:
					break;
				case ret:
					break;
				case pairmove:
					break;
				default:
					System.out.println("Cannot resolve the type");
					break;
				}
				thePredecessor = instr;
				index++;
			}
		}
		else{
			System.out.println("Null Instr List");
		}
		
	}
	
	public void printPredecessors(){
		if(this.instrList != null){
			if(!this.predecessors.isEmpty()){
				for(int i = 0; i < this.predecessors.size(); i++){
					System.out.print(i + " => " + instrList.get(i) + " :: ");
					ArrayList tmp = this.predecessors.get(i);
					if(!tmp.isEmpty()){
						for(int j = 0; j < tmp.size(); j++){
							System.out.print(tmp.get(j) + ", ");
						}
					}
					System.out.println();
					
					
				}
			}
			else{
				System.out.println("Empty Predecessors Mapping!");
			}
			
		}
		
	}
	
	public static int getInstructionTypeNum(Instruction ins){
		String type = ins.getClass().getName();
		if(type.equals("cn.edu.sjtu.dfa.Binop"))
			return binop;
		if(type.equals("cn.edu.sjtu.dfa.Call"))
			return call;
		if(type.equals("cn.edu.sjtu.dfa.CallWithReturn"))
			return callwithreturn;
		if(type.equals("cn.edu.sjtu.dfa.CJump"))
			return cjump;
		if(type.equals("cn.edu.sjtu.dfa.Jump"))
			return jump;
		if(type.equals("cn.edu.sjtu.dfa.Label"))
			return label;
		if(type.equals("cn.edu.sjtu.dfa.Move"))
			return move;
		if(type.equals("cn.edu.sjtu.dfa.Return"))
			return ret;
		if(type.equals("cn.edu.sjtu.dfa.PairMove"))
			return pairmove;
		System.out.println("Cannot resolve the type: " + type);
		return error;
	}
	
	public static void main(String args[]){
		DataFlowAnalyzer dfa = new DataFlowAnalyzer("E:\\Temp\\ContentProviderTest\\dfa", "E:\\Temp\\ContentProviderTest\\classes.dex");
		dfa.Analyse();
		dfa.Print();
		GraphBuilder gb = new GraphBuilder(DataFlowAnalyzer.ils.get("com.iftest.IfTest").get("public onCreate(Landroid/os/Bundle;)V"));
		gb.buildGraph();
		gb.printPredecessors();
	}
}
